package com.zhomboin.controller;

import com.zhomboin.common.excaption.BusinessException;
import com.zhomboin.domain.DatabaseConfig;
import com.zhomboin.domain.GeneratorRequest;
import com.zhomboin.domain.Result;
import com.zhomboin.domain.TableInfo;
import lombok.extern.slf4j.Slf4j;
import org.apache.tomcat.util.http.fileupload.FileUtils;
import org.mybatis.generator.api.MyBatisGenerator;
import org.mybatis.generator.config.*;
import org.mybatis.generator.internal.util.JavaBeansUtil;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

@Slf4j
@Validated
@RestController
@RequestMapping("/api")
public class GeneratorController {

    @PostMapping("/test-connection")
    public Result<List<TableInfo>> testConnection(@RequestBody DatabaseConfig config) {
        try (Connection conn = DriverManager.getConnection(
                config.getJdbcUrl(), config.getUsername(), config.getPassword())) {

            DatabaseMetaData metaData = conn.getMetaData();
            ResultSet rs = metaData.getTables(null, null, "%", new String[]{"TABLE"});
            log.debug("connect to {} success", config.getJdbcUrl());

            List<TableInfo> tables = new ArrayList<>();
            while (rs.next()) {
                TableInfo info = new TableInfo();
                info.setName(rs.getString("TABLE_NAME"));

                // 获取表统计信息（需要数据库权限）
                try (Statement stmt = conn.createStatement();
                     ResultSet countRs = stmt.executeQuery(
                             "SELECT COUNT(*) FROM " + info.getName())) {
                    if (countRs.next()) {
                        info.setRows(countRs.getLong(1));
                    }
                }

                tables.add(info);
            }
            return Result.success(tables);

        } catch (SQLException e) {
            throw new BusinessException(1001, "数据库连接失败: " + e.getMessage());
        }
    }

    @PostMapping(value = "/generate", produces = "application/zip")
    public ResponseEntity<byte[]> generateCode(@RequestBody GeneratorRequest request) {
        Path tempDir = null;
        Path zipPath = null;
        try {
            zipPath = Files.createTempFile("mybatis-code", ".zip");
            log.debug("生成临时压缩文件：{}", zipPath);

            // 创建临时目录
            tempDir = Files.createTempDirectory("generated-code");
            File generatedDir = tempDir.toFile();
            log.debug("临时目录创建成功: {}", generatedDir.getAbsolutePath());
            createTempDirectories(generatedDir);

            // 生成代码
            List<String> warnings = new ArrayList<>();
            Configuration config = buildMyBatisConfig(request, generatedDir);
            MyBatisGenerator generator = new MyBatisGenerator(config, null, warnings);
            generator.generate(null);
            if (!warnings.isEmpty()) {
                warnings.forEach(log::info);
            }

            // 验证生成结果
            File javaFile = new File(generatedDir, "src/main/java");
            validateDirectory(javaFile);
            File resourceDir = new File(generatedDir, "src/main/resources");
            validateDirectory(resourceDir);
            log.info("生成代码成功，开始打包");

            try (ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(zipPath.toFile()))) {
                // 将生成的代码文件添加到 ZIP
                addFilesToZip(generatedDir, generatedDir, zipOut);
                zipOut.finish();
            }

            // 计算hash值
            String hash = calculateSHA256(zipPath);

            FileSystemResource resource = new FileSystemResource(zipPath.toFile());
            log.info("打包完成，文件大小：{}KB", resource.contentLength() / 1024);

            return ResponseEntity.ok()
                    .header("Content-Disposition", "attachment; filename=mybatis-code.zip")
                    .header("X-Content-SHA256", hash)
                    .contentLength(resource.contentLength())
                    .body(resource.getContentAsByteArray());
        } catch (Exception e) {
            throw new BusinessException(1002, "代码生成失败: " + e.getMessage());
        } finally {
            // 清理临时压缩文件
            if (zipPath != null) {
                try {
                    Files.delete(zipPath);
                    log.debug("已清理临时压缩文件：{}", zipPath);
                } catch (IOException e) {
                    log.warn("清理临时压缩危机失败：{}", zipPath);
                }
            }
            // 清理临时目录
            if (tempDir != null) {
                try {
                    FileUtils.deleteDirectory(tempDir.toFile());
                    log.debug("已清理临时目录：{}", tempDir);
                } catch (IOException e) {
                    log.warn("临时目录清理失败：", e);
                }
            }
        }
    }

    private void createTempDirectories(File rootDir) throws IOException {
        File javaDir = new File(rootDir.getAbsolutePath(), "/src/main/java");
        Files.createDirectories(javaDir.toPath());
        log.debug("创建临时目录成功：{}", javaDir.getAbsolutePath());

        File resourceDir = new File(rootDir.getAbsolutePath(), "/src/main/resources");
        Files.createDirectories(resourceDir.toPath());
        log.debug("创建临时目录成功：{}", resourceDir.getAbsolutePath());
    }

    private Configuration buildMyBatisConfig(GeneratorRequest request, File tempDir) {
        // 动态构建 MyBatis Generator 配置
        Context context = new Context(ModelType.FLAT);
        context.setId("myContext");
        context.setTargetRuntime("MyBatis3Simple");
        context.addProperty("beginningDelimiter", "`");
        context.addProperty("endingDelimiter", "`");

        // 禁用所有 Example 配置
        context.addProperty("enableExampleMethods", "false");
        context.addProperty("generateExampleClass", "false");

        // 数据库连接配置
        JDBCConnectionConfiguration jdbcConfig = new JDBCConnectionConfiguration();
        jdbcConfig.setDriverClass("com.mysql.cj.jdbc.Driver");
        jdbcConfig.setConnectionURL(request.getJdbcUrl());
        jdbcConfig.setUserId(request.getUsername());
        jdbcConfig.setPassword(request.getPassword());
        context.setJdbcConnectionConfiguration(jdbcConfig);

        // 实体类配置
        JavaModelGeneratorConfiguration modelConfig = new JavaModelGeneratorConfiguration();
        modelConfig.setTargetPackage("com.zhomboin.domain");
        modelConfig.setTargetProject(new File(tempDir.getAbsolutePath(), "/src/main/java").getAbsolutePath());
        context.setJavaModelGeneratorConfiguration(modelConfig);

        // Mapper XML 配置
        SqlMapGeneratorConfiguration sqlMapConfig = new SqlMapGeneratorConfiguration();
        sqlMapConfig.setTargetPackage("mapper");
        sqlMapConfig.setTargetProject(new File(tempDir, "src/main/resources").getAbsolutePath());
        context.setSqlMapGeneratorConfiguration(sqlMapConfig);

        // Mapper 接口配置
        JavaClientGeneratorConfiguration clientConfig = new JavaClientGeneratorConfiguration();
        clientConfig.setTargetPackage("com.zhomboin.mapper");
        clientConfig.setConfigurationType("XMLMAPPER");
        clientConfig.setTargetProject(new File(tempDir.getAbsolutePath(), "/src/main/java").getAbsolutePath());
        context.setJavaClientGeneratorConfiguration(clientConfig);

        // 添加表
        for (String table : request.getTables()) {
            TableConfiguration tableConfig = new TableConfiguration(context);
            tableConfig.setTableName(table);
            tableConfig.setDomainObjectName(JavaBeansUtil.getCamelCaseString(table, true));

            // 禁用表级 Example
            tableConfig.addProperty("useActualColumnNames", "false");
            tableConfig.setCountByExampleStatementEnabled(false);
            tableConfig.setDeleteByExampleStatementEnabled(false);
            tableConfig.setSelectByExampleStatementEnabled(false);
            tableConfig.setUpdateByExampleStatementEnabled(false);

            context.addTableConfiguration(tableConfig);
        }

        Configuration config = new Configuration();
        config.addContext(context);
        return config;
    }

    private void validateDirectory(File dir) throws IOException {
        if (!dir.exists()) {
            throw new IOException("目录不存在: " + dir.getAbsolutePath());
        }
        if (!dir.isDirectory()) {
            throw new IOException("路径不是一个目录：" + dir.getAbsolutePath());
        }
        File[] files = dir.listFiles();
        if (files == null) {
            throw new IOException("无法读取目录内容：" + dir.getAbsolutePath());
        }
        if (files.length == 0) {
            throw new IOException("目录为空：" + dir.getAbsolutePath());
        }
    }

    private void addFilesToZip(File rootDir, File currentDir, ZipOutputStream zipOut) throws IOException {
        File[] files = currentDir.listFiles();
        if (files == null) {
            throw new IOException("无法读取目录内容：" + currentDir.getAbsolutePath());
        }
        if (files.length == 0) {
            throw new IOException("目录为空：" + currentDir.getAbsolutePath());
        }

        for (File file : files) {
            if (file.isDirectory()) {
                addFilesToZip(rootDir, file, zipOut);
                continue;
            }
            // 计算相对路径
            String relativePath = rootDir.toPath().relativize(file.toPath()).toString();

            // 创建ZIP条目，使用Unix风格路径
            ZipEntry zipEntry = new ZipEntry(relativePath.replace("\\", "/"));
            zipOut.putNextEntry(zipEntry);

            // 写入文件内容
            Files.copy(file.toPath(), zipOut);
            zipOut.closeEntry();
            log.debug("已添加文件: {}", relativePath);
        }
    }

    public static String calculateSHA256(Path filePath) throws IOException, NoSuchAlgorithmException {
        MessageDigest digest = MessageDigest.getInstance("SHA-256");
        try (InputStream is = Files.newInputStream(filePath)) {
            byte[] buffer = new byte[8192];
            int read;
            while ((read = is.read(buffer)) != -1) {
                digest.update(buffer, 0, read);
            }
        }
        return bytesToHex(digest.digest());
    }

    private static String bytesToHex(byte[] bytes) {
        StringBuilder hexString = new StringBuilder();
        for (byte b : bytes) {
            String hex = Integer.toHexString(0xff & b);
            if (hex.length() == 1) {
                hexString.append('0');
            }
            hexString.append(hex);
        }
        return hexString.toString();
    }
}
